﻿/********
* 
* 
*  Description:
*  Create By kain.hong at 7/20/2010 11:22:24 AM
*  
*
*  Revision History:
*  Date                  Who                 What
*  
* 
*/
#region namespace

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data;
using Cherry.Agile.Configuration;
using Microsoft.Practices.Unity;
using Cherry.Agile.Core;
using System.Diagnostics;

#endregion

namespace Cherry.Agile.Data.DataAccess {

    /// <summary>
    /// 数据连接上下文管理，线程安全（一个线程持有一个实例）。默认情况下Using范围内数据库调用共享一个有效连接。
    /// </summary>
    /// <example>
    /// <code>
    /// <![CDATA[
    /// private Remp.Security.User GetUser(Remp.Security.AccountTicket ticket, bool valiated)
    /// {
    ///     Remp.Security.User result;
    ///     using (var scope = new DbConnectionScope(Unity.REMPDATABASE))
    ///     {
    ///         if (result != null)
    ///         {
    ///             ticket.Id = result.ID;
    ///             result.Organizations = GetUserOrgs(ticket); //GetUserOrgs,GetUserRoles 共享一个数据库连接
    ///             result.Roles = GetUserRoles(ticket);
    ///         }
    ///     }
    ///     return result;
    /// }
    /// 
    /// private Remp.Security.Role[] GetUserRoles(Remp.Security.AccountTicket ticket)
    /// {
    ///      
    ///     List<FunctionInfo> functions;
    ///     List<RoleInfo> roles;
    ///    
    ///         using (var fo = new FunctionRepository())
    ///         {
    ///              //TODO:
    ///         }
    /// 
    ///         using (var repository = new RoleRepository())
    ///         {
    ///               //TODO:
    ///         }
    ///      
    /// 
    ///     return roles.Select(c => GetRole(c, functions)).ToArray();
    /// }
    /// 
    /// private Remp.Security.Organization[] GetUserOrgs(Remp.Security.AccountTicket ticket)
    /// {
    ///     
    ///         List<OrganizationInfo> lst;
    ///         using (var repository = new OrganizationRepository())
    ///         {
    ///              //TODO:
    ///         }               
    ///      
    /// }
    /// }]]>
    /// </code>
    /// </example>
    sealed public class DbConnectionScope : IDisposable {

        static ThreadLocal<Dictionary<string, DbConnectionWrapper>> __currentConnections = new ThreadLocal<Dictionary<string, DbConnectionWrapper>>(
                () => new Dictionary<string, DbConnectionWrapper>());

        static ThreadLocal<DbConnectionScope> __currentScope = new ThreadLocal<DbConnectionScope>(() => null);

        private DbConnectionScope _priorScope;
        private string _connectionString;
        DbConnectionWrapper DbConnectionWrapper { get; set; }

        static IInstrumentationProvider _instrumentationProvider;

        private string name;
        public IInstrumentationProvider InstrumentationProvider {
            get {
                return _instrumentationProvider;
            }
        }

        /// <summary>
        /// 当前上下文的实例
        /// </summary>
        public static DbConnectionScope Current {
            get {
                if (__currentScope.Value == null)
                    throw new NoNullAllowedException("must init db connection before used");// 访问数据连接时需先初始化连接");
                //new DbConnectionScope();
                return __currentScope.Value;
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        [Obsolete("please used DbConnectionScope(string)")]
        public DbConnectionScope() {
            string connectionString;

            if (System.Configuration.ConfigurationManager.ConnectionStrings.Count > 0)
                connectionString = "name=" + System.Configuration.ConfigurationManager.ConnectionStrings[0].Name;
            else
                throw new ArgumentNullException("database setting section's defaultDatabase attribute could not be null.");

            Init(connectionString);
        }

        /// <summary>
        /// 根据配置信息实例化
        /// </summary>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// using( var scope = new DbConnectionScope("name=myconnectionstring") )
        /// {
        ///     ///TODO:
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <param name="nameOrconnectionString">连接字符串,格式为name={连接字符串名称}</param>
        public DbConnectionScope(string nameOrconnectionString) {
            Init(nameOrconnectionString);
        }

        #region public methods
        /// <summary>
        /// 获取当前上下文管理的连接<see cref="DbConnection"/> 
        /// </summary>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// using( var scope = new DbConnectionScope("name=myconnectionstring") )
        /// {
        ///     var con = scope.GetConnection();
        ///     ///TODO:
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <returns>上下文持有的DbConnection</returns>
        public DbConnection GetConnection() {
            if (DbConnectionWrapper.IsDisposed)
                throw new ObjectDisposedException("DbConnection");
            var con = DbConnectionWrapper.Connection;
            return con;
        }

        /// <summary>
        /// 获取当前上下文管理的连接，如果连接已经关闭则打开<see cref="DbConnection"/> 
        /// </summary>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// using( var scope = new DbConnectionScope("name=myconnectionstring") )
        /// {
        ///     var con = scope.GetConnection();
        ///     ///TODO:
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <returns>上下文持有的已打开的DbConnection</returns>
        public DbConnection GetOpenedConnection() {
            var con = GetConnection();
            if (con.State != ConnectionState.Open) {
                try {
                    con.Open();
                } catch (EntityException ex) {
                    throw new System.InvalidOperationException("unconnected db server,make sure db server is ok.", ex);//"连接数据库错误，请确认数据库连接是否正常");
                }

            }
            return con;
        }

        /// <summary>
        /// 标志当前实例是否已经Disposed
        /// </summary>
        public bool IsDisposed {
            get { return DbConnectionWrapper == null; }
        }

        /// <summary>
        /// Disposed当前实例<see cref="IDisposable"/>
        /// </summary>
        public void Dispose() {
            if (!IsDisposed) {

                if (__currentScope.Value == this)// inner thread
                {
                    DbConnectionScope prior = _priorScope;
                    while (null != prior && prior.IsDisposed) {
                        prior = prior._priorScope;
                    }
                    __currentScope.Value = prior;

                    this.DbConnectionWrapper = null;
                } else {
                    throw new InvalidOperationException("this scope can't be dispose in current thread,beacuse it has been created by another thread.");
                }

                var conwrap = __currentConnections.Value[_connectionString];
                if (!conwrap.IsDisposed)
                    conwrap.Dispose();

                if (conwrap.IsDisposed)
                    __currentConnections.Value.Remove(_connectionString);
                if (InstrumentationProvider != null)
                    InstrumentationProvider.FireConnectionDisposed();

                if (__currentConnections.Value.Keys.Count == 0) {
                    __currentConnections.Value = null;
                    GC.SuppressFinalize(this);
                    Trace.TraceInformation(string.Format("DbConnectionScope\t{0:G} connection scope disposed", DateTime.Now));
                }
            }
        }
        #endregion

        #region private methods
        private void Init(string connectionString) {
            DbConnectionWrapper wrapper = null;
            if (__currentConnections.Value == null)
                __currentConnections.Value = new Dictionary<string, DbConnectionWrapper>();
            this.name = connectionString.Split('=')[1];

            lock (Container.Current) {
                if (Container.Current.IsRegistered<IInstrumentationProvider>())
                    _instrumentationProvider = Container.Current.Resolve<IInstrumentationProvider>(
                        new ParameterOverride("instanceName", name));
            }

            if (!__currentConnections.Value.TryGetValue(connectionString, out wrapper)) {
                var con = DbProviderFactory.GetDbConnection(connectionString);
                System.Diagnostics.Trace.TraceInformation("DbConnectionScope\t{0:G} {1} initial connecion scope&connection.", DateTime.Now, con.GetHashCode());

                wrapper = new DbConnectionWrapper(con);

                wrapper.Disposed += (o, e) => {
                    if (__currentConnections.Value != null && __currentConnections.Value.ContainsKey(connectionString))
                        __currentConnections.Value.Remove(connectionString);
                    //if (!IsDisposed)
                    //{
                    //    __currentConnections.Value = null;
                    //    __currentScope.Value = null;
                    //    GC.SuppressFinalize(this);
                    //}
                };
                if (this.InstrumentationProvider != null)
                    InstrumentationProvider.FireConnectionCreated();
                __currentConnections.Value.Add(connectionString, wrapper);
            } else {
                wrapper.AddRef();
                System.Diagnostics.Trace.TraceInformation("DbConnectionScope\tlinked connection:" + connectionString);
            }

            DbConnectionWrapper = wrapper;

            _priorScope = __currentScope.Value;

            __currentScope.Value = this;

            _connectionString = connectionString;


        }


        #endregion
    }
}
